#ifndef __BV_BOUNDS_H__
#define __BV_BOUNDS_H__

namespace inf {
    /*
    ===============================================================================

    Axis Aligned Bounding Box

    ===============================================================================
    */

    class Bounds {
    public:
        Bounds( void );
        explicit Bounds( const Vec3 &mins, const Vec3 &maxs );
        explicit Bounds( const Vec3 &point );

        const Vec3 &	operator[]( const int index ) const;
        Vec3 &		operator[]( const int index );
        Bounds		operator+( const Vec3 &t ) const;				// returns translated bounds
        Bounds &		operator+=( const Vec3 &t );					// translate the bounds
        Bounds		operator*( const Mat3 &r ) const;				// returns rotated bounds
        Bounds &		operator*=( const Mat3 &r );					// rotate the bounds
        Bounds		operator+( const Bounds &a ) const;
        Bounds &		operator+=( const Bounds &a );
        Bounds		operator-( const Bounds &a ) const;
        Bounds &		operator-=( const Bounds &a );

        bool			compare( const Bounds &a ) const;							// exact compare, no epsilon
        bool			compare( const Bounds &a, const float epsilon ) const;	// compare with epsilon
        bool			operator==(	const Bounds &a ) const;						// exact compare, no epsilon
        bool			operator!=(	const Bounds &a ) const;						// exact compare, no epsilon

        void			Clear( void );									// inside out bounds
        void			zero( void );									// single point at origin

        Vec3			GetCenter( void ) const;						// returns center of bounds
        float			GetRadius( void ) const;						// returns the radius relative to the bounds origin
        float			GetRadius( const Vec3 &center ) const;		// returns the radius relative to the given center
        float			GetVolume( void ) const;						// returns the volume of the bounds
        bool			IsCleared( void ) const;						// returns true if bounds are inside out

        bool			AddPoint( const Vec3 &v );					// add the point, returns true if the bounds expanded
        bool			AddBounds( const Bounds &a );					// add the bounds, returns true if the bounds expanded
        Bounds		    Intersect( const Bounds &a ) const;			// return intersection of this bounds with the given bounds
        Bounds &		IntersectSelf( const Bounds &a );				// intersect this bounds with the given bounds
        Bounds		    Expand( const float d ) const;					// return bounds expanded in all directions with the given value
        Bounds &		ExpandSelf( const float d );					// expand bounds in all directions with the given value
        Bounds		    Translate( const Vec3 &translation ) const;	// return translated bounds
        Bounds &		TranslateSelf( const Vec3 &translation );		// translate this bounds
        Bounds		    Rotate( const Mat3 &rotation ) const;			// return rotated bounds
        Bounds &		RotateSelf( const Mat3 &rotation );			// rotate this bounds

        float			PlaneDistance( const Plane &plane ) const;
        int				PlaneSide( const Plane &plane, const float epsilon = math::FLOAT_EPSILON ) const;

        bool			ContainsPoint( const Vec3 &p ) const;			// includes touching
        bool			IntersectsBounds( const Bounds &a ) const;	// includes touching
        bool			LineIntersection( const Vec3 &start, const Vec3 &end ) const;
        // intersection point is start + dir * scale
        bool			RayIntersection( const Vec3 &start, const Vec3 &dir, float &scale ) const;

        // most tight bounds for the given transformed bounds
        void			FromTransformedBounds( const Bounds &bounds, const Vec3 &origin, const Mat3 &axis );
        // most tight bounds for a point set
        //void			FromPoints( const Vec3 *points, const int numPoints );
        // most tight bounds for a translation
        void			FromPointTranslation( const Vec3 &point, const Vec3 &translation );
        void			FromBoundsTranslation( const Bounds &bounds, const Vec3 &origin, const Mat3 &axis, const Vec3 &translation );
        // most tight bounds for a rotation
        void			FromPointRotation( const Vec3 &point, const Rotation &rotation );
        void			FromBoundsRotation( const Bounds &bounds, const Vec3 &origin, const Mat3 &axis, const Rotation &rotation );

        void			ToPoints( Vec3 points[8] ) const;
        Sphere		    ToSphere( void ) const;

        void			AxisProjection( const Vec3 &dir, float &min, float &max ) const;
        void			AxisProjection( const Vec3 &origin, const Mat3 &axis, const Vec3 &dir, float &min, float &max ) const;

        static          Bounds ZERO;
    private:
        Vec3			b[2];
    };

    INF_INLINE Bounds::Bounds( void ) {
    }

    INF_INLINE Bounds::Bounds( const Vec3 &mins, const Vec3 &maxs ) {
        b[0] = mins;
        b[1] = maxs;
    }

    INF_INLINE Bounds::Bounds( const Vec3 &point ) {
        b[0] = point;
        b[1] = point;
    }

    INF_INLINE const Vec3 &Bounds::operator[]( const int index ) const {
        return b[index];
    }

    INF_INLINE Vec3 &Bounds::operator[]( const int index ) {
        return b[index];
    }

    INF_INLINE Bounds Bounds::operator+( const Vec3 &t ) const {
        return Bounds( b[0] + t, b[1] + t );
    }

    INF_INLINE Bounds &Bounds::operator+=( const Vec3 &t ) {
        b[0] += t;
        b[1] += t;
        return *this;
    }

    INF_INLINE Bounds Bounds::operator*( const Mat3 &r ) const {
        Bounds bounds;
        bounds.FromTransformedBounds( *this, Vec3::ORIGIN, r );
        return bounds;
    }

    INF_INLINE Bounds &Bounds::operator*=( const Mat3 &r ) {
        this->FromTransformedBounds( *this, Vec3::ORIGIN, r );
        return *this;
    }

    INF_INLINE Bounds Bounds::operator+( const Bounds &a ) const {
        Bounds newBounds;
        newBounds = *this;
        newBounds.AddBounds( a );
        return newBounds;
    }

    INF_INLINE Bounds &Bounds::operator+=( const Bounds &a ) {
        Bounds::AddBounds( a );
        return *this;
    }

    INF_INLINE Bounds Bounds::operator-( const Bounds &a ) const {
        assert( b[1][0] - b[0][0] > a.b[1][0] - a.b[0][0] &&
            b[1][1] - b[0][1] > a.b[1][1] - a.b[0][1] &&
            b[1][2] - b[0][2] > a.b[1][2] - a.b[0][2] );
        return Bounds( Vec3( b[0][0] + a.b[1][0], b[0][1] + a.b[1][1], b[0][2] + a.b[1][2] ),
            Vec3( b[1][0] + a.b[0][0], b[1][1] + a.b[0][1], b[1][2] + a.b[0][2] ) );
    }

    INF_INLINE Bounds &Bounds::operator-=( const Bounds &a ) {
        assert( b[1][0] - b[0][0] > a.b[1][0] - a.b[0][0] &&
            b[1][1] - b[0][1] > a.b[1][1] - a.b[0][1] &&
            b[1][2] - b[0][2] > a.b[1][2] - a.b[0][2] );
        b[0] += a.b[1];
        b[1] += a.b[0];
        return *this;
    }

    INF_INLINE bool Bounds::compare( const Bounds &a ) const {
        return ( b[0].compare( a.b[0] ) && b[1].compare( a.b[1] ) );
    }

    INF_INLINE bool Bounds::compare( const Bounds &a, const float epsilon ) const {
        return ( b[0].compare( a.b[0], epsilon ) && b[1].compare( a.b[1], epsilon ) );
    }

    INF_INLINE bool Bounds::operator==( const Bounds &a ) const {
        return compare( a );
    }

    INF_INLINE bool Bounds::operator!=( const Bounds &a ) const {
        return !compare( a );
    }

    INF_INLINE void Bounds::Clear( void ) {
        b[0][0] = b[0][1] = b[0][2] = math::INFINITY;
        b[1][0] = b[1][1] = b[1][2] = -math::INFINITY;
    }

    INF_INLINE void Bounds::zero( void ) {
        b[0][0] = b[0][1] = b[0][2] =
            b[1][0] = b[1][1] = b[1][2] = 0;
    }

    INF_INLINE Vec3 Bounds::GetCenter( void ) const {
        return Vec3( ( b[1][0] + b[0][0] ) * 0.5f, ( b[1][1] + b[0][1] ) * 0.5f, ( b[1][2] + b[0][2] ) * 0.5f );
    }

    INF_INLINE float Bounds::GetVolume( void ) const {
        if ( b[0][0] >= b[1][0] || b[0][1] >= b[1][1] || b[0][2] >= b[1][2] ) {
            return 0.0f;
        }
        return ( ( b[1][0] - b[0][0] ) * ( b[1][1] - b[0][1] ) * ( b[1][2] - b[0][2] ) );
    }

    INF_INLINE bool Bounds::IsCleared( void ) const {
        return b[0][0] > b[1][0];
    }

    INF_INLINE bool Bounds::AddPoint( const Vec3 &v ) {
        bool expanded = false;
        if ( v[0] < b[0][0]) {
            b[0][0] = v[0];
            expanded = true;
        }
        if ( v[0] > b[1][0]) {
            b[1][0] = v[0];
            expanded = true;
        }
        if ( v[1] < b[0][1] ) {
            b[0][1] = v[1];
            expanded = true;
        }
        if ( v[1] > b[1][1]) {
            b[1][1] = v[1];
            expanded = true;
        }
        if ( v[2] < b[0][2] ) {
            b[0][2] = v[2];
            expanded = true;
        }
        if ( v[2] > b[1][2]) {
            b[1][2] = v[2];
            expanded = true;
        }
        return expanded;
    }

    INF_INLINE bool Bounds::AddBounds( const Bounds &a ) {
        bool expanded = false;
        if ( a.b[0][0] < b[0][0] ) {
            b[0][0] = a.b[0][0];
            expanded = true;
        }
        if ( a.b[0][1] < b[0][1] ) {
            b[0][1] = a.b[0][1];
            expanded = true;
        }
        if ( a.b[0][2] < b[0][2] ) {
            b[0][2] = a.b[0][2];
            expanded = true;
        }
        if ( a.b[1][0] > b[1][0] ) {
            b[1][0] = a.b[1][0];
            expanded = true;
        }
        if ( a.b[1][1] > b[1][1] ) {
            b[1][1] = a.b[1][1];
            expanded = true;
        }
        if ( a.b[1][2] > b[1][2] ) {
            b[1][2] = a.b[1][2];
            expanded = true;
        }
        return expanded;
    }

    INF_INLINE Bounds Bounds::Intersect( const Bounds &a ) const {
        Bounds n;
        n.b[0][0] = ( a.b[0][0] > b[0][0] ) ? a.b[0][0] : b[0][0];
        n.b[0][1] = ( a.b[0][1] > b[0][1] ) ? a.b[0][1] : b[0][1];
        n.b[0][2] = ( a.b[0][2] > b[0][2] ) ? a.b[0][2] : b[0][2];
        n.b[1][0] = ( a.b[1][0] < b[1][0] ) ? a.b[1][0] : b[1][0];
        n.b[1][1] = ( a.b[1][1] < b[1][1] ) ? a.b[1][1] : b[1][1];
        n.b[1][2] = ( a.b[1][2] < b[1][2] ) ? a.b[1][2] : b[1][2];
        return n;
    }

    INF_INLINE Bounds &Bounds::IntersectSelf( const Bounds &a ) {
        if ( a.b[0][0] > b[0][0] ) {
            b[0][0] = a.b[0][0];
        }
        if ( a.b[0][1] > b[0][1] ) {
            b[0][1] = a.b[0][1];
        }
        if ( a.b[0][2] > b[0][2] ) {
            b[0][2] = a.b[0][2];
        }
        if ( a.b[1][0] < b[1][0] ) {
            b[1][0] = a.b[1][0];
        }
        if ( a.b[1][1] < b[1][1] ) {
            b[1][1] = a.b[1][1];
        }
        if ( a.b[1][2] < b[1][2] ) {
            b[1][2] = a.b[1][2];
        }
        return *this;
    }

    INF_INLINE Bounds Bounds::Expand( const float d ) const {
        return Bounds( Vec3( b[0][0] - d, b[0][1] - d, b[0][2] - d ),
            Vec3( b[1][0] + d, b[1][1] + d, b[1][2] + d ) );
    }

    INF_INLINE Bounds &Bounds::ExpandSelf( const float d ) {
        b[0][0] -= d;
        b[0][1] -= d;
        b[0][2] -= d;
        b[1][0] += d;
        b[1][1] += d;
        b[1][2] += d;
        return *this;
    }

    INF_INLINE Bounds Bounds::Translate( const Vec3 &translation ) const {
        return Bounds( b[0] + translation, b[1] + translation );
    }

    INF_INLINE Bounds &Bounds::TranslateSelf( const Vec3 &translation ) {
        b[0] += translation;
        b[1] += translation;
        return *this;
    }

    INF_INLINE Bounds Bounds::Rotate( const Mat3 &rotation ) const {
        Bounds bounds;
        bounds.FromTransformedBounds( *this, Vec3::ORIGIN, rotation );
        return bounds;
    }

    INF_INLINE Bounds &Bounds::RotateSelf( const Mat3 &rotation ) {
        FromTransformedBounds( *this, Vec3::ORIGIN, rotation );
        return *this;
    }

    INF_INLINE bool Bounds::ContainsPoint( const Vec3 &p ) const {
        if ( p[0] < b[0][0] || p[1] < b[0][1] || p[2] < b[0][2]
        || p[0] > b[1][0] || p[1] > b[1][1] || p[2] > b[1][2] ) {
            return false;
        }
        return true;
    }

    INF_INLINE bool Bounds::IntersectsBounds( const Bounds &a ) const {
        if ( a.b[1][0] < b[0][0] || a.b[1][1] < b[0][1] || a.b[1][2] < b[0][2]
        || a.b[0][0] > b[1][0] || a.b[0][1] > b[1][1] || a.b[0][2] > b[1][2] ) {
            return false;
        }
        return true;
    }

    INF_INLINE Sphere Bounds::ToSphere( void ) const {
        Sphere sphere;
        sphere.SetOrigin( ( b[0] + b[1] ) * 0.5f );
        sphere.SetRadius( ( b[1] - sphere.GetOrigin() ).length() );
        return sphere;
    }

    INF_INLINE void Bounds::AxisProjection( const Vec3 &dir, float &min, float &max ) const {
        float d1, d2;
        Vec3 center, extents;

        center = ( b[0] + b[1] ) * 0.5f;
        extents = b[1] - center;

        d1 = dir * center;
        d2 = math::fabs( extents[0] * dir[0] ) +
            math::fabs( extents[1] * dir[1] ) +
            math::fabs( extents[2] * dir[2] );

        min = d1 - d2;
        max = d1 + d2;
    }

    INF_INLINE void Bounds::AxisProjection( const Vec3 &origin, const Mat3 &axis, const Vec3 &dir, float &min, float &max ) const {
        float d1, d2;
        Vec3 center, extents;

        center = ( b[0] + b[1] ) * 0.5f;
        extents = b[1] - center;
        center = origin + center * axis;

        d1 = dir * center;
        d2 = math::fabs( extents[0] * ( dir * axis[0] ) ) +
            math::fabs( extents[1] * ( dir * axis[1] ) ) +
            math::fabs( extents[2] * ( dir * axis[2] ) );

        min = d1 - d2;
        max = d1 + d2;
    }

}   //::inf

#endif /* !__BV_BOUNDS_H__ */
